/*  README:
*       Algoritmo de MonteCarlo(Sequencial)
*
*           Algoritmo que converge aleatoriamente com precisao de 10.000 casas decimais,
*           portanto temos 25 iteracoes para seu calculo na main;
*           para alterar a quantidade de iteracoes deve-se alterar o iterador de 25 para o desejado na funcao;
*           o tempo eh fornecido em milisegundos,; defina a precisao na variavel "prec"
*
*/

#include<stdio.h>
#include<math.h>
#include<gmp.h>
#include<pthread.h>
#include<time.h>

void *calculaPInternos(int *PIn);

void monteCarlo(int n){
    pthread_t  thread[9];   /*Array com as threads utilizadas*/

    int iret1, iret2, i;
    int pin, Pin[9];
    char string[FILENAME_MAX];

    /*Declara as variaveis do tipo float de alta precisao*/
    mpf_t PIn;
    mpf_t PTotal;
    mpf_t Pi;

    /*Inicializa as variaveis com a precisao declarada anteriormente*/
    mpf_init(PIn);
    mpf_init(PTotal);
    mpf_init(Pi);

    /*Gera o algoritmo que sera utilizado para a geracao dos numeros randomicos entre 0 e 1 do tipo float de alta precisao*/
    gmp_randstate_t r_state;
    gmp_randinit_mt (r_state);
    gmp_randseed_ui(r_state, time(NULL));

    /*Loop com a criacao das 9 threads utilizadas para o calculo da quantidade de pontos internos*/
    for(i = 0; i < 9;i++)
        iret1 = pthread_create( &thread[i], NULL, (void *)calculaPInternos, (void *)&Pin[i]);

    for(i = 0; i < 9;i++)
        pthread_join( thread[i], NULL);

    /*Loop com a criacao das 9 threads utilizadas para o calculo da quantidade de pontos internos*/
    for(i = 0; i < 9;i++)
        pin += Pin[i];

    /*Converte os valores inteiros dos pontos internos e do total para float de alta precisao*/
    sprintf(string, "%d", pin);
    mpf_set_str(PIn, string, 10);
    sprintf(string, "%d", n);
    mpf_set_str(PTotal, string, 10);

    gmp_printf("PIn: %Ff \n",PIn);
    gmp_printf("PTotal: %Ff \n",PTotal);

    /*Multiplicao por 4 para chegar no valor aproximado de pi*/
    mpf_div(Pi, PIn, PTotal);
    mpf_set_str(PTotal, "4", 10);
    mpf_mul(Pi, Pi, PTotal);

    gmp_printf("%.50Ff\n",Pi);

    gmp_randclear(r_state);
    mpf_clears(PIn, PTotal, Pi, NULL);
}

/**
*Funcao que calcula a quantidade total de pontos internos
*
*/

void *calculaPInternos(int *PIn){
    int i;

    /*Variaveis de alta precisao criadas e inicializadas para verificar se estao no circulo*/
    mpf_t check;
    mpf_t rand_NumX;
    mpf_t rand_NumY;

    mpf_init(check);
    mpf_init(rand_NumX);
    mpf_init(rand_NumY);

    /*InicialIzao do algoritmo randomico*/
    gmp_randstate_t state;
    gmp_randinit_mt (state);
    gmp_randseed_ui(state, time(NULL));

    /*Loop do total de pontos dividido por 9*/
    for(i = 1 ; i <  111111; i++){
        /*Armazena em rand_NumX e rand_NumY os numeros randomicos atraves da variavel r_state iniciada anteriormente*/
        mpf_urandomb(rand_NumX,state,100000);
        mpf_urandomb(rand_NumY,state,100000);

       /*Armazena em check o resultado de (x^2)+(y^2)*/
        mpf_pow_ui(rand_NumX,rand_NumX,2);
        mpf_pow_ui(rand_NumY,rand_NumY,2);
        mpf_add(check, rand_NumX, rand_NumY);

        /*Verifica se check eh menor ou igual a 1; Caso afirmativo, incrementa a variavel pin que contem a quantidade de pontos dentro do circulo*/
        mpf_set_str(rand_NumX, "1", 10);
        if(mpf_cmp(check, rand_NumX) < 0)
            (*PIn)++;
    }

    mpf_clears(check, rand_NumX, rand_NumY, NULL);
    pthread_exit(NULL);
}

int main(){
    clock_t tInicio, tFim;
    tInicio = clock();

    /*Define a precisao juntamente com a quantidade de pontos dentro do circulo*/
    mpf_set_default_prec(10000);

    /*Funcao com o parametro do total de pontos a serem testados*/
    monteCarlo(999999);

    tFim = clock();
    printf("\nTempo Decorrido:%ld ms\n\n", (tFim-tInicio)/(CLOCKS_PER_SEC/1000));

    return 0;
}
